package com.nageoffer.shortlink.admin.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.shortlink.admin.common.convention.exception.ClientException;
import com.nageoffer.shortlink.admin.common.convention.exception.ServiceException;
import com.nageoffer.shortlink.admin.common.enums.UserErrorCodeEnum;
import com.nageoffer.shortlink.admin.dao.entity.UserContext;
import com.nageoffer.shortlink.admin.dao.entity.UserDO;
import com.nageoffer.shortlink.admin.dao.mapper.UserMapper;
import com.nageoffer.shortlink.admin.dto.req.UserLoginRequestDTO;
import com.nageoffer.shortlink.admin.dto.req.UserRegisterRequestDTO;
import com.nageoffer.shortlink.admin.dto.req.UserUpdateRequestDTO;
import com.nageoffer.shortlink.admin.dto.resp.UserRespDTO;
import com.nageoffer.shortlink.admin.service.UserService;
import com.nageoffer.shortlink.admin.utils.PasswordEncoderUtils;
import lombok.AllArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.servlet.http.HttpSession;
import java.util.concurrent.TimeUnit;

/**
 * 用户接口实现层
 */
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate redisTemplate;

    @Override
    public UserRespDTO getUserByUsername(String username) {
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username);
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        if (userDO == null) {
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        UserRespDTO result = new UserRespDTO();
        BeanUtils.copyProperties(userDO, result);
        return result;
    }

    @Override
    public Boolean hasUsername(String username) {
       return !userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    public void register(UserRegisterRequestDTO userRegisterRequestDTO) {
        // 1. 参数校验
        String username = userRegisterRequestDTO.getUsername();
        String USER_REGISTER_KEY="short-link:user_register:"+username;
        if(StrUtil.isBlank( username)){
            throw new ServiceException("用户名不能为空");
        }

        // 2. 判断用户名是否已存在
        if (!hasUsername(username)) {
            throw new ClientException(UserErrorCodeEnum.USER_EXIST);
        }

        // 利用分布式锁方式防止海量用户注册
        RLock rLock = redissonClient.getLock(USER_REGISTER_KEY);
        try {
            if(rLock.tryLock()){
                UserDO userDO = new UserDO();
                // 3. 数据封装并入库
                BeanUtils.copyProperties(userRegisterRequestDTO, userDO);
                // 对密码进行加密
                userDO.setPassword(PasswordEncoderUtils.encode(userDO.getPassword()));
                int insert = baseMapper.insert(userDO);
                if (insert <= 0) {
                    throw new ClientException(UserErrorCodeEnum.USER_REGISTER_FAIL);
                }
                // 4. 将用户名添加到布隆过滤器中
                userRegisterCachePenetrationBloomFilter.add(username);
            }else {
                throw new ClientException(UserErrorCodeEnum.USER_EXIST);
            }
        } finally {
            if(rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }
    }

    @Override
    public void update(UserUpdateRequestDTO userUpdateRequestDTO) {
        String username = userUpdateRequestDTO.getUsername();
        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userUpdateRequestDTO, userDO);
        // 判断是否是当前登录用户
        if (!username.equals(UserContext.getCurrentUser().getUsername())) {
            throw new ServiceException("被修改用户非当前登录用户");
        }
        // 密码加密
        userDO.setPassword(PasswordEncoderUtils.encode(userDO.getPassword()));
        int update = baseMapper.update(userDO, Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username));

        if (update <= 0) {
            throw new ClientException(UserErrorCodeEnum.USER_UPDATE_FAIL);
        }
    }

    @Override
    public UserRespDTO login(UserLoginRequestDTO userLoginRequestDTO, HttpSession session) {
        // 1. 参数校验
        String username = userLoginRequestDTO.getUsername();
        String password = userLoginRequestDTO.getPassword();
        if(StrUtil.isBlank(username)){
            throw new ServiceException("用户名不能为空");
        }
        if(StrUtil.isBlank(password)){
            throw new ServiceException("密码不能为空");
        }
        // 2. 判断用户名是否存在
        if (hasUsername(username)) {
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        UserDO userDO = baseMapper.selectOne(Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username));
        // 3. 密码校验
        if (!PasswordEncoderUtils.matches(password, userDO.getPassword())) {
            throw new ClientException(UserErrorCodeEnum.USER_PASSWORD_ERROR);
        }
        
        // 4. 设置session
        session.setAttribute("username", username);
        session.setMaxInactiveInterval(30 * 60); // 30分钟过期
        
        // 5. 将用户信息存入UserContext
        UserContext.setUser(userDO);
        
        // 6. 返回用户信息
        UserRespDTO result = new UserRespDTO();
        BeanUtils.copyProperties(userDO, result);
        return result;
    }

    @Override
    public void logout(HttpSession session) {
        // 将session标记为无效并存入Redis
        String sessionId = session.getId();
        redisTemplate.opsForValue().set("session:blacklist:" + sessionId, "1", 30, TimeUnit.MINUTES);
        session.invalidate();
    }
    
    /**
     * 从当前会话中获取用户信息
     * @return UserRespDTO 用户信息
     */
    public UserDO getCurrentUserInfo() {
        UserDO userDO = UserContext.getCurrentUser();
        if (userDO == null) {
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        return userDO;
    }
}